package cn.edu.hitsz.compiler.parser;

import cn.edu.hitsz.compiler.NotImplementedException;
import cn.edu.hitsz.compiler.ir.IRImmediate;
import cn.edu.hitsz.compiler.ir.IRValue;
import cn.edu.hitsz.compiler.ir.IRVariable;
import cn.edu.hitsz.compiler.ir.Instruction;
import cn.edu.hitsz.compiler.lexer.Token;
import cn.edu.hitsz.compiler.parser.table.Attribute;
import cn.edu.hitsz.compiler.parser.table.Production;
import cn.edu.hitsz.compiler.parser.table.Status;
import cn.edu.hitsz.compiler.parser.table.Term;
import cn.edu.hitsz.compiler.symtab.SymbolTable;
import cn.edu.hitsz.compiler.utils.FileUtils;

import java.util.ArrayList;
import java.util.List;

// TODO: 实验三: 实现 IR 生成

/**
 *
 */
public class IRGenerator implements ActionObserver {
    private List<Instruction> inst = new ArrayList<Instruction>() ;
    /** 属性栈 */
    private List<Attribute> attr = new ArrayList<Attribute>();

    /** 根据文法属性生成对应的IRValue */
    private IRValue toIR(Attribute E){
        IRValue res;
        // 立即数 or 变量
        if(E.getName().matches("[0-9]*")){
            res = IRImmediate.of(Integer.parseInt(E.getName()));
        }
        else {
            res = IRVariable.named(E.getName());
        }
        return res;
    }

    @Override
    public void whenShift(Status currentStatus, Token currentToken) {
        attr.add(new Attribute(currentToken.getText()));
    }

    @Override
    public void whenReduce(Status currentStatus, Production production) {
        switch (production.index()) {
            case 6 -> { // S -> id = E;
                /** get E.name; pop =; get id.text; gen(MOV, id.text, E.name) */
                Attribute E = attr.remove(attr.size()-1);
                attr.remove(attr.size()-1);
                Attribute id =  attr.remove(attr.size()-1);

                IRVariable res = IRVariable.named(id.getName());

                IRValue from = toIR(E);

                inst.add(Instruction.createMov(res,from));

            }

            case 7  -> {  // S -> return E;
                /** get E.name; pop return; gen(RET,, E.name) */
                Attribute E = attr.remove(attr.size()-1);
                attr.remove(attr.size()-1);

                IRValue res = toIR(E);

                attr.add(new Attribute(E.getName()));
                inst.add(Instruction.createRet(res));


            }
            case 8  -> { // E -> E + A;
                /** get A.name; pop +; get E1.name; E.name = tempReg(); gen(ADD, E.name, E1.name, A.name) */
                Attribute A = attr.remove(attr.size()-1);
                attr.remove(attr.size()-1);
                Attribute E1 =  attr.remove(attr.size()-1);

                IRValue lhs = toIR(E1);
                IRValue rhs = toIR(A);

                // IR二元运算符的操作数不允许出现IR立即数，因此先将IR立即数加载到新的寄存器变量中
                if(lhs.isImmediate()){
                    IRVariable t = IRVariable.temp();
                    inst.add(Instruction.createMov(t,lhs));
                    lhs = t;
                }
                if(rhs.isImmediate()){
                    IRVariable t = IRVariable.temp();
                    inst.add(Instruction.createMov(t,rhs));
                    rhs = t;
                }

                IRVariable res = IRVariable.temp();
                attr.add(new Attribute(res.getName()));
                inst.add(Instruction.createAdd(res,lhs,rhs));

            }
            case 9  -> { // E -> E - A;
                /** get A.name; pop -; get E1.name; E.name = tempReg(); gen(SUB, E.name, E1.name, A.name) */
                Attribute A = attr.remove(attr.size()-1);
                attr.remove(attr.size()-1);
                Attribute E1 =  attr.remove(attr.size()-1);

                IRValue lhs = toIR(E1);
                IRValue rhs = toIR(A);

                // IR二元运算符的操作数不允许出现IR立即数，因此先将IR立即数加载到新的寄存器变量中
                if(lhs.isImmediate()){
                    IRVariable t = IRVariable.temp();
                    inst.add(Instruction.createMov(t,lhs));
                    lhs = t;
                }
                if(rhs.isImmediate()){
                    IRVariable t = IRVariable.temp();
                    inst.add(Instruction.createMov(t,rhs));
                    rhs = t;
                }

                IRVariable res = IRVariable.temp();

                attr.add(new Attribute(res.getName()));
                inst.add(Instruction.createSub(res,lhs,rhs));

            }
            case 11 -> { // A -> A * B;
                /** get B.name; pop *; get A1.name; A.name = tempReg(); gen(MUL, A.name, A1.name, B.name) */
                Attribute B = attr.remove(attr.size()-1);
                attr.remove(attr.size()-1);
                Attribute A1 =  attr.remove(attr.size()-1);

                IRValue lhs = toIR(A1);
                IRValue rhs = toIR(B);

                // IR二元运算符的操作数不允许出现IR立即数，因此先将IR立即数加载到新的寄存器变量中
                if(lhs.isImmediate()){
                    IRVariable t = IRVariable.temp();
                    inst.add(Instruction.createMov(t,lhs));
                    lhs = t;
                }
                if(rhs.isImmediate()){
                    IRVariable t = IRVariable.temp();
                    inst.add(Instruction.createMov(t,rhs));
                    rhs = t;
                }

                IRVariable res = IRVariable.temp();

                attr.add(new Attribute(res.getName()));
                inst.add(Instruction.createMul(res,lhs,rhs));
            }

            case 13 -> { // B -> ( E );
                /** pop ); get E.name; pop (; B.name = E.name */
                attr.remove(attr.size()-1);
                Attribute at =  attr.remove(attr.size()-1);
                attr.remove(attr.size()-1);
                attr.add(at);
            }

            default -> {
            }
        }
    }


    @Override
    public void whenAccept(Status currentStatus) {

    }

    @Override
    public void setSymbolTable(SymbolTable table) {
    }

    public List<Instruction> getIR() {
        return this.inst;
    }

    public void dumpIR(String path) {
        FileUtils.writeLines(path, getIR().stream().map(Instruction::toString).toList());
    }
}

